"""
错误处理与重试机制模块
"""

import time
import logging
import traceback
from typing import Dict, Any, Callable, Optional, Type, Union
from functools import wraps
from enum import Enum
import json
from pathlib import Path
from datetime import datetime

class ErrorType(Enum):
    """错误类型枚举"""
    NETWORK_ERROR = "network_error"
    MODEL_INFERENCE_ERROR = "model_inference_error"
    MEMORY_ERROR = "memory_error"
    FILE_ERROR = "file_error"
    TIMEOUT_ERROR = "timeout_error"
    UNKNOWN_ERROR = "unknown_error"

class RetryStrategy:
    """重试策略"""
    
    def __init__(self, max_retries: int = 3, base_delay: float = 1.0, 
                 max_delay: float = 60.0, exponential_base: float = 2.0):
        self.max_retries = max_retries
        self.base_delay = base_delay
        self.max_delay = max_delay
        self.exponential_base = exponential_base
    
    def get_delay(self, attempt: int) -> float:
        """获取重试延迟时间"""
        delay = self.base_delay * (self.exponential_base ** attempt)
        return min(delay, self.max_delay)
    
    def should_retry(self, attempt: int, error: Exception) -> bool:
        """判断是否应该重试"""
        if attempt >= self.max_retries:
            return False
        
        # 某些错误类型不应该重试
        non_retryable_errors = [
            FileNotFoundError,
            PermissionError,
            ValueError,  # 参数错误
        ]
        
        for error_type in non_retryable_errors:
            if isinstance(error, error_type):
                return False
        
        return True

class ErrorHandler:
    """错误处理器"""
    
    def __init__(self, config: Dict[str, Any]):
        """初始化错误处理器"""
        self.config = config
        self.logger = logging.getLogger('image_cropping_system')
        
        # 重试配置
        processing_config = config.get('processing', {})
        self.max_retries = processing_config.get('max_retries', 3)
        self.timeout = processing_config.get('timeout', 120)
        
        # 创建重试策略
        self.retry_strategies = {
            ErrorType.NETWORK_ERROR: RetryStrategy(max_retries=5, base_delay=2.0),
            ErrorType.MODEL_INFERENCE_ERROR: RetryStrategy(max_retries=3, base_delay=5.0),
            ErrorType.MEMORY_ERROR: RetryStrategy(max_retries=2, base_delay=10.0),
            ErrorType.FILE_ERROR: RetryStrategy(max_retries=1, base_delay=1.0),
            ErrorType.TIMEOUT_ERROR: RetryStrategy(max_retries=2, base_delay=3.0),
            ErrorType.UNKNOWN_ERROR: RetryStrategy(max_retries=1, base_delay=1.0),
        }
        
        # 错误日志
        self.error_log = []
        self.max_log_size = 1000
        
        # 创建错误日志目录
        self.log_dir = Path('output/logs')
        self.log_dir.mkdir(parents=True, exist_ok=True)
        self.error_log_file = self.log_dir / 'error_log.json'
    
    def classify_error(self, error: Exception) -> ErrorType:
        """分类错误类型"""
        error_message = str(error).lower()
        error_type = type(error).__name__
        
        # 网络错误
        if any(keyword in error_message for keyword in ['connection', 'network', 'http', 'request']):
            return ErrorType.NETWORK_ERROR
        
        # 模型推理错误
        if any(keyword in error_message for keyword in ['ollama', 'model', 'inference', 'ai']):
            return ErrorType.MODEL_INFERENCE_ERROR
        
        # 内存错误
        if any(keyword in error_message for keyword in ['memory', 'ram', 'out of memory']):
            return ErrorType.MEMORY_ERROR
        
        # 文件错误
        if any(keyword in error_message for keyword in ['file', 'directory', 'path', 'not found']):
            return ErrorType.FILE_ERROR
        
        # 超时错误
        if any(keyword in error_message for keyword in ['timeout', 'time out']):
            return ErrorType.TIMEOUT_ERROR
        
        return ErrorType.UNKNOWN_ERROR
    
    def handle_error(self, error: Exception, context: Dict[str, Any] = None) -> Dict[str, Any]:
        """处理错误"""
        error_type = self.classify_error(error)
        timestamp = datetime.now().isoformat()
        
        error_info = {
            'timestamp': timestamp,
            'error_type': error_type.value,
            'error_message': str(error),
            'error_class': type(error).__name__,
            'traceback': traceback.format_exc(),
            'context': context or {},
            'severity': self._get_error_severity(error_type)
        }
        
        # 记录错误日志
        self._log_error(error_info)
        
        # 根据错误类型采取不同的处理策略
        self._apply_error_strategy(error_type, error, context)
        
        return error_info
    
    def _get_error_severity(self, error_type: ErrorType) -> str:
        """获取错误严重程度"""
        severity_map = {
            ErrorType.NETWORK_ERROR: 'medium',
            ErrorType.MODEL_INFERENCE_ERROR: 'high',
            ErrorType.MEMORY_ERROR: 'high',
            ErrorType.FILE_ERROR: 'medium',
            ErrorType.TIMEOUT_ERROR: 'medium',
            ErrorType.UNKNOWN_ERROR: 'low'
        }
        return severity_map.get(error_type, 'low')
    
    def _log_error(self, error_info: Dict[str, Any]) -> None:
        """记录错误日志"""
        try:
            # 添加到内存日志
            self.error_log.append(error_info)
            
            # 限制日志大小
            if len(self.error_log) > self.max_log_size:
                self.error_log = self.error_log[-self.max_log_size:]
            
            # 写入文件日志
            with open(self.error_log_file, 'a', encoding='utf-8') as f:
                f.write(json.dumps(error_info, ensure_ascii=False) + '\n')
            
            # 记录到系统日志
            self.logger.error(
                f"错误发生 - 类型: {error_info['error_type']}, "
                f"消息: {error_info['error_message']}, "
                f"严重程度: {error_info['severity']}"
            )
            
        except Exception as e:
            self.logger.error(f"记录错误日志时发生错误: {e}")
    
    def _apply_error_strategy(self, error_type: ErrorType, error: Exception, 
                            context: Dict[str, Any] = None) -> None:
        """应用错误处理策略"""
        if error_type == ErrorType.MEMORY_ERROR:
            # 内存错误：尝试垃圾回收
            import gc
            gc.collect()
            self.logger.info("因内存错误触发垃圾回收")
        
        elif error_type == ErrorType.NETWORK_ERROR:
            # 网络错误：记录网络状态
            self.logger.warning("检测到网络错误，建议检查网络连接")
        
        elif error_type == ErrorType.MODEL_INFERENCE_ERROR:
            # 模型推理错误：检查模型状态
            self.logger.warning("模型推理失败，建议检查Ollama服务状态")
        
        elif error_type == ErrorType.FILE_ERROR:
            # 文件错误：检查文件权限
            self.logger.warning("文件操作失败，建议检查文件权限和路径")
    
    def retry_with_backoff(self, func: Callable, *args, 
                          retry_strategy: Optional[RetryStrategy] = None,
                          context: Dict[str, Any] = None, **kwargs) -> Any:
        """带退避的重试机制"""
        if retry_strategy is None:
            retry_strategy = RetryStrategy(max_retries=self.max_retries)
        
        last_error = None
        
        for attempt in range(retry_strategy.max_retries + 1):
            try:
                return func(*args, **kwargs)
                
            except Exception as error:
                last_error = error
                error_type = self.classify_error(error)
                
                # 记录重试信息
                self.logger.warning(
                    f"函数 {func.__name__} 执行失败 (尝试 {attempt + 1}/{retry_strategy.max_retries + 1}): {error}"
                )
                
                # 判断是否应该重试
                if not retry_strategy.should_retry(attempt, error):
                    break
                
                # 等待重试
                delay = retry_strategy.get_delay(attempt)
                self.logger.info(f"等待 {delay:.2f} 秒后重试...")
                time.sleep(delay)
        
        # 所有重试都失败了，处理错误
        error_info = self.handle_error(last_error, context)
        raise last_error
    
    def get_error_statistics(self) -> Dict[str, Any]:
        """获取错误统计信息"""
        if not self.error_log:
            return {
                'total_errors': 0,
                'error_types': {},
                'severity_distribution': {},
                'recent_errors': []
            }
        
        # 统计错误类型
        error_types = {}
        severity_distribution = {}
        
        for error in self.error_log:
            error_type = error['error_type']
            severity = error['severity']
            
            error_types[error_type] = error_types.get(error_type, 0) + 1
            severity_distribution[severity] = severity_distribution.get(severity, 0) + 1
        
        # 获取最近的错误
        recent_errors = self.error_log[-10:] if len(self.error_log) > 10 else self.error_log
        
        return {
            'total_errors': len(self.error_log),
            'error_types': error_types,
            'severity_distribution': severity_distribution,
            'recent_errors': recent_errors
        }
    
    def clear_error_log(self) -> None:
        """清空错误日志"""
        self.error_log.clear()
        try:
            if self.error_log_file.exists():
                self.error_log_file.unlink()
        except Exception as e:
            self.logger.error(f"清空错误日志文件时发生错误: {e}")

def retry_on_error(max_retries: int = 3, delay: float = 1.0, 
                  exponential_backoff: bool = True,
                  exceptions: tuple = (Exception,)):
    """重试装饰器"""
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs):
            last_error = None
            
            for attempt in range(max_retries + 1):
                try:
                    return func(*args, **kwargs)
                    
                except exceptions as error:
                    last_error = error
                    
                    if attempt == max_retries:
                        break
                    
                    # 计算延迟时间
                    if exponential_backoff:
                        current_delay = delay * (2 ** attempt)
                    else:
                        current_delay = delay
                    
                    time.sleep(current_delay)
            
            raise last_error
        
        return wrapper
    return decorator

def timeout_handler(seconds: int):
    """超时处理装饰器"""
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, **kwargs):
            import signal
            
            def timeout_handler(signum, frame):
                raise TimeoutError(f"函数 {func.__name__} 执行超时 ({seconds}秒)")
            
            # 设置超时信号
            signal.signal(signal.SIGALRM, timeout_handler)
            signal.alarm(seconds)
            
            try:
                result = func(*args, **kwargs)
                return result
            finally:
                signal.alarm(0)  # 取消超时
        
        return wrapper
    return decorator